package com.envoy.testsuite.mvc;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.EnumMap;

import com.envoy.testsuite.dummydevice.DummyDevice;

/**
 * Tests the system with some dummy devices.
 * @author Angus J. Goldsmith
 *
 */
public class DeviceManagerModel implements Model {

  /**
   * The server url.
   */
  private String serverURL;
  /**
   * List of dummy devices.
   */
  //private ArrayList<DummyDevice> dummyDevices;

  /**
   * Sets up the tests.
   */
  public DeviceManagerModel() {
    //dummyDevices = new ArrayList<DummyDevice>();
    serverURL = "http://localhost:8080/EnvoyServer/TestServlet";
  }

  /**
   * Runs the tests.
   */
  public final void run() {
    final double latitude = 98, longitude = -1.2;
    final int accuracy = 1;
    createNewDevice("123", "Angus", latitude, longitude, accuracy,
        "08/04/2013");
  }

  /**
   * Creates a new device and registers it with the server.
   * @param id the id of the device.
   * @param name the name of the user.
   * @param latitude the latitude of the device.
   * @param longitude the longidue of the device.
   * @param accuracy the accuracy of the GPS data.
   * @param date the date of the GPS data.
   */
  private void createNewDevice(final String id, final String name,
      final double latitude, final double longitude, final int accuracy,
      final String date) {
    DummyDevice newDevice = new DummyDevice(id);
    newDevice.nameRequest(serverURL, name);
    newDevice.locationRequest(serverURL, latitude, longitude, accuracy, date);
    newDevice.deviceIdRequest(serverURL);
  }

  /**
   * Sends a message into the void.
   * @param id the id of the sender.
   * @param destId the id of the recipient.
   * @param messageContent the content of the message.
   * @param latitude the latitude it was sent from.
   * @param longitude the longitude it was sent from.
   * @param accuracy the accuracy of the GPS data.
   * @param date the date of the GPS data.
   */
  private void sendMessage(final String id, final String destId,
      final String messageContent, final double latitude,
      final double longitude, final int accuracy, final String date) {
    DummyDevice device = new DummyDevice(id);
    device.newMessageRequest(serverURL, destId, messageContent, latitude,
        longitude, accuracy, date);
  }

  /**
   * Checks the server is available.
   * @return the status of the server ie if it can connect or not.
   */
  @Override
  public final ModelStatus setup() {
    final int timeout = 200;
    if (ping(serverURL, timeout)) {
      return ModelStatus.Ready;
    } else {
      return ModelStatus.NotConnected;
    }
  }

  /**
   * Checks the operation to perform and gives the parameters to the relevant
   * method.
   * @param operationData the data for the operation.
   * @return the results of the operation.
   */
  @Override
  public final ModelTransferObject<ViewKey, UpdateType> performOperation(
      final ModelTransferObject<Parameter, OperationType> operationData) {

    ModelTransferObject<ViewKey, UpdateType> results;
    UpdateType updateType;
    EnumMap<ViewKey, String> resultData =
        new EnumMap<ViewKey, String>(ViewKey.class);
    OperationType operationType = operationData.getDataType();
    

    switch(operationType) {
      case createNewDevice:
        String id = operationData.getValue(Parameter.DeviceId);
        String name = operationData.getValue(Parameter.Name);
        double latitude =
            Double.parseDouble(operationData.getValue(Parameter.Latitude));
        double longitude =
            Double.parseDouble(operationData.getValue(Parameter.Longitude));
        int accuracy =
            Integer.parseInt(operationData.getValue(Parameter.Accuracy));
        String date = operationData.getValue(Parameter.GPSDate);

        createNewDevice(id, name, latitude, longitude, accuracy, date);
        updateType = UpdateType.deviceCreation;
        resultData.put(ViewKey.operationSuccess, "true");
        break;

      case sendMessage:
        id = operationData.getValue(Parameter.DeviceId);
        String destId = operationData.getValue(Parameter.DestinationId);
        String messageContent =
            operationData.getValue(Parameter.MessageContent);
        latitude =
            Double.parseDouble(operationData.getValue(Parameter.Latitude));
        longitude =
            Double.parseDouble(operationData.getValue(Parameter.Longitude));
        accuracy = Integer.parseInt(operationData.getValue(Parameter.Accuracy));
        date = operationData.getValue(Parameter.GPSDate);

        sendMessage(id, destId, messageContent, latitude, longitude, accuracy,
            date);
        updateType = UpdateType.messageSent;
        resultData.put(ViewKey.operationSuccess, "true");
        break;

      default:
        System.out.println("Invalid operation type!");
        updateType = UpdateType.invalid;
        resultData.put(ViewKey.operationSuccess, "false");
        break;
    }
    results =
        new ModelTransferObject<ViewKey, UpdateType>(updateType, resultData);
    return results;
  }

  /**
   * Pings a server.  Credit to BalusC on Stack Overflow.
   * @author BalusC, http://stackoverflow.com/questions/3584210/
   *         preferred-java-way-to-ping-a-http-url-for-availability
   * @param url the URL to ping.
   * @param timeout the timeout length.
   * @return wether or not the server is available.
   */
  public final boolean ping(final String url, final int timeout) {
    String modifiedUrl = url.replaceFirst("https", "http");

    try {
      HttpURLConnection connection =
          (HttpURLConnection) new URL(modifiedUrl).openConnection();
      connection.setConnectTimeout(timeout);
      connection.setReadTimeout(timeout);
      connection.setRequestMethod("HEAD");
      int responseCode = connection.getResponseCode();
      final int minValidCode = 200, maxValidCode = 399;
      return (minValidCode <= responseCode && responseCode <= maxValidCode);
    } catch (IOException exception) {
      return false;
    }
  }
}
